package com.bugsnag.ohos;

/**
 * Event
 *
 * @since 2021-07-01
 */

import com.bugsnag.ohos.annotation.NonNull;
import com.bugsnag.ohos.internal.ImmutableConfig;
import com.bugsnag.ohos.util.OhosLog;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * An Event object represents a Throwable captured by Bugsnag and is available as a parameter on
 * an {@link OnErrorCallback}, where individual properties can be mutated before an error report is
 * sent to Bugsnag's API.
 */
@SuppressWarnings("ConstantConditions")
public class Event implements JsonStream.Streamable, MetadataAware, UserAware {
    private final EventInternal impl;
    private final Logger logger;

    Event(Throwable originalError, ImmutableConfig config, SeverityReason severityReason, Logger logger) {
        this(originalError, config, severityReason, new Metadata(), logger);
    }

    Event(Throwable originalError, ImmutableConfig config, SeverityReason severityReason,
          Metadata metadata, Logger logger) {
        this(new EventInternal(originalError, config, severityReason, metadata), logger);
    }

    Event(EventInternal impl, Logger logger) {
        this.impl = impl;
        this.logger = logger;
    }

    private void logNull(String property) {
        logger.e("Invalid null value supplied to config." + property + ", ignoring");
    }

    /**
     * The Throwable object that caused the event in your application.
     * <p>
     * Manipulating this field does not affect the error information reported to the
     * Bugsnag dashboard. Use {@link Event#getErrors()} to access and amend the representation of
     * the error that will be sent.
     *
     * @return original error
     */

    public Throwable getOriginalError() {
        return impl.getOriginalError();
    }

    /**
     * Information extracted from the {@link Throwable} that caused the event can be found in this
     * field. The list contains at least one {@link Error} that represents the thrown object
     * with subsequent elements in the list populated from {@link Throwable#getCause()}.
     * <p>
     * A reference to the actual {@link Throwable} object that caused the event is available
     * through {@link Event#getOriginalError()} ()}.
     *
     * @return errors
     */
    @NonNull
    public List<Error> getErrors() {
        return impl.getErrors();
    }

    /**
     * If thread state is being captured along with the event, this field will contain a
     * list of {@link Thread} objects.
     *
     * @return threads
     */
    @NonNull
    public List<Thread> getThreads() {
        return impl.getThreads();
    }

    /**
     * A list of breadcrumbs leading up to the event. These values can be accessed and amended
     * if necessary. See {@link Breadcrumb} for details of the data available.
     *
     * @return breadcrumbs
     */
    @NonNull
    public List<Breadcrumb> getBreadcrumbs() {
        return impl.getBreadcrumbs();
    }

    /**
     * Information set by the notifier about your app can be found in this field. These values
     * can be accessed and amended if necessary.
     *
     * @return app with state
     */
    @NonNull
    public AppWithState getApp() {
        return impl.getApp();
    }

    /**
     * Information set by the notifier about your device can be found in this field. These values
     * can be accessed and amended if necessary.
     *
     * @return Device With State
     */
    @NonNull
    public DeviceWithState getDevice() {
        return impl.getDevice();
    }

    /**
     * The API key used for events sent to Bugsnag. Even though the API key is set when Bugsnag
     * is initialized, you may choose to send certain events to a different Bugsnag project.
     *
     * @param apiKey
     */
    public void setApiKey(@NonNull String apiKey) {
        if (apiKey != null) {
            impl.setApiKey(apiKey);
        } else {
            logNull("apiKey");
        }
    }

    /**
     * The API key used for events sent to Bugsnag. Even though the API key is set when Bugsnag
     * is initialized, you may choose to send certain events to a different Bugsnag project.
     *
     * @return api key
     */
    @NonNull
    public String getApiKey() {
        return impl.getApiKey();
    }

    /**
     * The severity of the event. By default, unhandled exceptions will be {@link Severity#ERROR}
     * and handled exceptions sent with {@link Bugsnag#notify} {@link Severity#WARNING}.
     *
     * @param severity
     */
    public void setSeverity(@NonNull Severity severity) {
        if (severity != null) {
            impl.setSeverity(severity);
        } else {
            logNull("severity");
        }
    }

    /**
     * The severity of the event. By default, unhandled exceptions will be {@link Severity#ERROR}
     * and handled exceptions sent with {@link Bugsnag#notify} {@link Severity#WARNING}.
     *
     * @return Severity
     */
    @NonNull
    public Severity getSeverity() {
        return impl.getSeverity();
    }

    /**
     * Set the grouping hash of the event to override the default grouping on the dashboard.
     * All events with the same grouping hash will be grouped together into one error. This is an
     * advanced usage of the library and mis-using it will cause your events not to group properly
     * in your dashboard.
     * <p>
     * As the name implies, this option accepts a hash of sorts.
     *
     * @param groupingHash
     */
    public void setGroupingHash(String groupingHash) {
        impl.setGroupingHash(groupingHash);
    }

    /**
     * Set the grouping hash of the event to override the default grouping on the dashboard.
     * All events with the same grouping hash will be grouped together into one error. This is an
     * advanced usage of the library and mis-using it will cause your events not to group properly
     * in your dashboard.
     * <p>
     * As the name implies, this option accepts a hash of sorts.
     *
     * @return grouping hash
     */

    public String getGroupingHash() {
        return impl.getGroupingHash();
    }

    /**
     * Sets the context of the error. The context is a summary what what was occurring in the
     * application at the time of the crash, if available, such as the visible activity.
     *
     * @param context
     */
    public void setContext(String context) {
        impl.setContext(context);
    }

    /**
     * Returns the context of the error. The context is a summary what what was occurring in the
     * application at the time of the crash, if available, such as the visible activity.
     *
     * @return context
     */

    public String getContext() {
        return impl.getContext();
    }

    /**
     * Sets the user associated with the event.
     *
     * @param id
     * @param email
     * @param name
     */
    @Override
    public void setUser(String id, String email, String name) {
        impl.setUser(id, email, name);
    }

    /**
     * Returns the currently set User information.
     *
     * @return user
     */
    @Override
    @NonNull
    public User getUser() {
        return impl.getUser();
    }

    /**
     * Adds a map of multiple metadata key-value pairs to the specified section.
     *
     * @param section
     * @param value
     */
    @Override
    public void addMetadata(@NonNull String section, @NonNull Map<String, Object> value) {
        if (section != null && value != null) {
            impl.addMetadata(section, value);
        } else {
            logNull("addMetadata");
        }
    }

    /**
     * Adds the specified key and value in the specified section. The value can be of
     * any primitive type or a collection such as a map, set or array.
     *
     * @param section
     * @param key
     * @param value
     */
    @Override
    public void addMetadata(@NonNull String section, @NonNull String key, Object value) {
        if (section != null && key != null) {
            impl.addMetadata(section, key, value);
        } else {
            logNull("addMetadata");
        }
    }

    /**
     * Removes all the data from the specified section.
     *
     * @param section
     */
    @Override
    public void clearMetadata(@NonNull String section) {
        if (section != null) {
            impl.clearMetadata(section);
        } else {
            logNull("clearMetadata");
        }
    }

    /**
     * Removes data with the specified key from the specified section.
     *
     * @param section
     * @param key
     */
    @Override
    public void clearMetadata(@NonNull String section, @NonNull String key) {
        if (section != null && key != null) {
            impl.clearMetadata(section, key);
        } else {
            logNull("clearMetadata");
        }
    }

    /**
     * Returns a map of data in the specified section.
     *
     * @return meta data
     */
    @Override

    public Map<String, Object> getMetadata(@NonNull String section) {
        if (section != null) {
            return impl.getMetadata(section);
        } else {
            logNull("getMetadata");
            return null;
        }
    }

    /**
     * Returns the value of the specified key in the specified section.
     *
     * @return meata data
     */
    @Override
    public Object getMetadata(@NonNull String section, @NonNull String key) {
        if (section != null && key != null) {
            return impl.getMetadata(section, key);
        } else {
            logNull("getMetadata");
            return null;
        }
    }

    /**
     * to stream
     *
     * @param stream
     * @throws IOException
     */
    @Override
    public void toStream(@NonNull JsonStream stream) throws IOException {
        OhosLog.d("Event", "toStream ...%s", stream);
        impl.toStream(stream);
    }

    /**
     * Whether the event was a crash (i.e. unhandled) or handled error in which the system
     * continued running.
     * <p>
     * Unhandled errors count towards your stability score. If you don't want certain errors
     * to count towards your stability score, you can alter this property through an
     * {@link OnErrorCallback}.
     *
     * @return is unhandled
     */
    public boolean isUnhandled() {
        return impl.getUnhandled();
    }

    /**
     * Whether the event was a crash (i.e. unhandled) or handled error in which the system
     * continued running.
     * <p>
     * Unhandled errors count towards your stability score. If you don't want certain errors
     * to count towards your stability score, you can alter this property through an
     * {@link OnErrorCallback}.
     *
     * @param unhandled
     */
    public void setUnhandled(boolean unhandled) {
        impl.setUnhandled(unhandled);
    }

    /**
     * show discard class
     *
     * @return boolean
     */
    protected boolean shouldDiscardClass() {
        return impl.shouldDiscardClass();
    }

    /**
     * update Severity Internal
     *
     * @param severity
     */
    protected void updateSeverityInternal(@NonNull Severity severity) {
        impl.updateSeverityInternal(severity);
    }

    /**
     * set app with state
     *
     * @param app
     */
    public void setApp(@NonNull AppWithState app) {
        impl.setApp(app);
    }

    /**
     * set device with state
     *
     * @param device
     */
    public void setDevice(@NonNull DeviceWithState device) {
        impl.setDevice(device);
    }

    void setBreadcrumbs(@NonNull List<Breadcrumb> breadcrumbs) {
        impl.setBreadcrumbs(breadcrumbs);
    }

    Session getSession() {
        return impl.session;
    }

    void setSession(Session session) {
        impl.session = session;
    }

    EventInternal getImpl() {
        return impl;
    }
}

